這裡要來介紹
https://laihao.vaserver.com/hairdressing
裡面的顧客管理
就是customers得部分
https://laihao.vaserver.com/customers

這裡先來講解一下美髮系統跟美容系統的routes
美容系統
https://laihao.vaserver.com/spa-intro
就是/home/laihao/public_html/routes/web.php裡的
這段程式碼是 Laravel 的 routes/web.php 檔案,定義了整個網站的 URL 路由規則。你特別標記的這一段,是「美髮預約系統」與「SPA 美容系統」的核心路由。
以下我將這段程式碼分成幾個邏輯區塊,逐行解釋:
// 預約管理(美髮預約系統)
Route::resource('appointments', 'AppointmentController')->except(['show']);
appointments,由 AppointmentController 處理。except(['show']) 表示不產生 GET /appointments/{id} 這個「單一預約詳情」的路由。index, create, store, edit, update, destroy 等路由。// 顧客管理(美髮預約系統)
Route::resource('customers', 'CustomerController')->except(['show']);
// 設計師管理(美髮預約系統)
Route::resource('staff', 'StaffController')->except(['show']);
// 服務管理(美髮預約系統)
Route::resource('services', 'ServiceController')->except(['show']);
// 商品管理(美髮預約系統)
Route::resource('hairdressingproducts', 'HairdressingProductController')
->except(['show'])
->parameters(['hairdressingproducts' => 'product']);
parameters(['hairdressingproducts' => 'product']) 這行很重要:它把 URL 中的參數名稱從 hairdressingproducts 改成 product。
/hairdressingproducts/{hairdressingproduct}/edit
/hairdressingproducts/{product}/edit
$product 變數來接收,語意更清晰。// 發票管理(美髮預約系統)
Route::resource('invoices', 'InvoiceController')->except(['show']);
// 使用者管理(美髮預約系統)
Route::resource('hairdressingusers', 'HairdressingUserController')->except(['show']);
// 預約服務明細(美髮預約系統)
Route::resource('appointmentservices', 'AppointmentServiceController')->except(['show']);
// 收據明細(美髮預約系統)
Route::resource('invoiceitems', 'InvoiceItemController')->except(['show']);
// 庫存異動紀錄(美髮預約系統)
Route::resource('inventorylogs', 'InventoryLogController')->except(['show']);
// 設計師排班(美髮預約系統)
Route::resource('staffschedules', 'StaffScheduleController')->except(['show']);
// 美髮系統總覽首頁
Route::get('/hairdressing', 'HairdressingDashboardController@index')->name('hairdressing.dashboard');
/hairdressing 時,由 HairdressingDashboardController 的 index 方法處理。name('hairdressing.dashboard') 給這個路由一個名稱,之後在程式碼中可以用 route('hairdressing.dashboard') 來產生 URL,不用硬編寫路徑。Route::middleware('auth.spa')->prefix('spa')->group(function () {
middleware('auth.spa'):這整個區塊的路由都需要通過 spa 認證守衛(guard)的驗證,也就是使用者必須先登入 SPA 系統才能訪問。prefix('spa'):所有路由的 URL 前面都會加上 /spa 前綴。group(function () { ... }):把多條路由包在一起,共用 middleware 和 prefix。 Route::get('/menu', function () {
return view('spa.menu', [
'user' => \Illuminate\Support\Facades\Auth::guard('spa')->user(),
]);
})->name('spa.menu');
/spa/menu 時,直接回傳 spa.menu 這個 view。Auth::guard('spa')->user() 取得)傳入 view 中,讓頁面可以顯示使用者資訊。 Route::get('/dashboard', [SpaDashboardController::class, 'index'])->name('spa.dashboard');
/spa/dashboard 時,由 SpaDashboardController 的 index 方法處理,通常是 SPA 系統的後台總覽頁面。 Route::get('/appointments', [SpaAppointmentController::class, 'index'])->name('spa.appointments.index');
/spa/appointments 時,顯示預約列表頁面。 Route::middleware('spa.role:admin,receptionist')->group(function () {
admin 或 receptionist 的使用者才能訪問區塊內的路由。spa.role:admin,receptionist 是自定義的 middleware,用來檢查使用者的角色權限。 Route::get('/appointments/create', [SpaAppointmentController::class, 'create'])->name('spa.appointments.create');
Route::post('/appointments', [SpaAppointmentController::class, 'store'])->name('spa.appointments.store');
Route::get('/appointments/{appointment}/edit', [SpaAppointmentController::class, 'edit'])->name('spa.appointments.edit');
Route::put('/appointments/{appointment}', [SpaAppointmentController::class, 'update'])->name('spa.appointments.update');
Route::delete('/appointments/{appointment}', [SpaAppointmentController::class, 'destroy'])->name('spa.appointments.destroy');
Route::resource 的例外方式處理過,這裡可能是為了更精細的權限控制或自訂流程而手動寫出。 Route::resource('customers', 'Spa\CustomerController')->names('spa.customers');
Route::resource('invoices', 'Spa\InvoiceController')->names('spa.invoices');
names('spa.customers') 會為所有產生的路由名稱加上 spa.customers. 前綴,例如 spa.customers.index, spa.customers.create 等。 Route::middleware('spa.role:admin')->group(function () {
admin 角色的使用者才能訪問的區塊(更高權限的功能)。 Route::resource('inventory-logs', SpaInventoryLogController::class);
Route::resource('products', '\App\Http\Controllers\Spa\SpaProductController')->names('spa.products');
Route::resource('users', 'Spa\SpaUserController')->names('spa.users');
Route::resource('services', 'Spa\ServiceController')->names('spa.services');
Route::resource('staff', 'Spa\StaffController')->names('spa.staff');
Route::resource('staff-schedules', 'Spa\StaffScheduleController')->names('spa.staff-schedules');
Route::resource 都會自動產生 7 條路由(index, create, store, show, edit, update, destroy)。 }); // 結束 spa.role:admin 區塊
}); // 結束 spa.role:admin,receptionist 區塊
Route::get('/appointments/{appointment}', [SpaAppointmentController::class, 'show'])->name('spa.appointments.show');
auth.spa 區塊內,但在角色限制區塊外,表示「所有已登入的 SPA 使用者」都可以查看單一預約詳情。 Route::middleware('spa.role:admin')->group(function () {
Route::resource('inventory-logs', 'Spa\InventoryLogController')->names('spa.inventory-logs');
Route::resource('inventory-logs', 'Spa\InventoryLogController');
});
inventory-logs 路由(可能是程式碼整理時的遺留),實際上第二條會被忽略或造成衝突,建議清理。// 補回:美容系統登入/登出路由(不需要 auth.spa middleware,因為登入前用不到)
Route::get('/spa-login', [SpaAuthController::class, 'showLoginForm'])->name('spa.login');
Route::post('/spa-login', [SpaAuthController::class, 'login'])->name('spa.login.submit');
Route::post('/spa-logout', [SpaAuthController::class, 'logout'])->name('spa.logout');
auth.spa middleware,因為使用者在登入前還不是認證使用者。GET /spa-login:顯示登入表單。POST /spa-login:提交登入表單,進行認證。POST /spa-logout:登出。Route::get('/spa-intro', function () {
return view('spa.intro');
})->name('spa.intro');
| 路由前綴 | 用途 | 權限要求 |
|---|---|---|
/appointments, /customers, /staff 等 |
美髮預約系統 CRUD | 無(需自行在 Controller 或 middleware 檢查) |
/hairdressing |
美髮系統總覽 | 無 |
/spa/* |
SPA 美容系統後台 | 需 auth.spa 登入 |
/spa/* 內的 admin,receptionist 區塊 |
預約、顧客、發票管理 | 需 admin 或 receptionist 角色 |
/spa/* 內的 admin 區塊 |
產品、使用者、排班等管理 | 僅 admin 角色 |
/spa-login, /spa-logout, /spa-intro |
SPA 登入/登出/介紹 | 無(公開) |
然後就來 customers 起手式:
從customers資料表開始~
然後是MVC的部分~
customers資料表
-- phpMyAdmin SQL Dump
-- version 5.2.3
-- https://www.phpmyadmin.net/
--
-- 主機: localhost:3306
-- 產生時間: 2026 年 09 月 08 日 12:23
-- 伺服器版本: 5.7.44
-- PHP 版本: 8.1.34
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- 資料庫: `laihao_Hairdressing`
--
-- --------------------------------------------------------
--
-- 資料表結構 `customers`
--
CREATE TABLE `customers` (
`id` bigint(20) UNSIGNED NOT NULL,
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`phone` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`gender` enum('male','female','other') COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`birthday` date DEFAULT NULL,
`notes` text COLLATE utf8mb4_unicode_ci,
`allergy_notes` text COLLATE utf8mb4_unicode_ci,
`hair_notes` text COLLATE utf8mb4_unicode_ci,
`is_member` tinyint(1) NOT NULL DEFAULT '0',
`member_no` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- 傾印資料表的資料 `customers`
--
INSERT INTO `customers` (`id`, `name`, `phone`, `email`, `gender`, `birthday`, `notes`, `allergy_notes`, `hair_notes`, `is_member`, `member_no`, `created_at`, `updated_at`) VALUES
(1, '王小婷', '0912345678', 'ting.wang@gmail.com', 'female', '1995-03-12', '偏好安靜服務', '對部分染膏氣味較敏感', '髮量多、自然捲、曾漂髮', 1, 'M20260001', '2026-08-22 03:00:52', '2026-08-22 03:00:52'),
(2, '陳雅雯', '0922111222', 'yawin.chen@gmail.com', 'female', '1990-07-25', '常做護髮', NULL, '髮質細軟、易扁塌', 1, 'M20260002', '2026-08-22 03:00:52', '2026-08-22 03:00:52'),
(3, '林先生', '0933555666', 'lin.man@gmail.com', 'male', '1988-11-02', '固定每月修剪', NULL, '短髮、兩側推高', 0, NULL, '2026-08-22 03:00:52', '2026-08-22 03:00:52'),
(4, '張可晴', '0977888999', 'keqing.zhang@gmail.com', 'female', '2000-09-18', '喜歡韓系髮型', '頭皮較敏感', '中長髮、曾染深棕色', 1, 'M20260003', '2026-08-22 03:00:52', '2026-08-22 03:00:52'),
(5, '黃TZU', '0612345678', 'huang123@gmail.com', 'female', '2002-02-07', NULL, NULL, NULL, 0, NULL, '2026-08-24 14:41:57', '2026-09-07 22:10:05');
--
-- 已傾印資料表的索引
--
--
-- 資料表索引 `customers`
--
ALTER TABLE `customers`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `customers_member_no_unique` (`member_no`),
ADD KEY `customers_phone_index` (`phone`);
--
-- 在傾印的資料表使用自動遞增(AUTO_INCREMENT)
--
--
-- 使用資料表自動遞增(AUTO_INCREMENT) `customers`
--
ALTER TABLE `customers`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;